import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
import { IWonewSer } from "@/typing";
import IHelper from "@/services/helper.service";
import { defer, ReplaySubject, concatMap, map, of, catchError, Observable, tap, timeout, from } from "rxjs";
import * as puppeteer from "puppeteer"
import IGlobal from "@/services/global.service";
import axios from "axios";
import ILogger from "@/services/logger.service";

@Injectable()
export class DyBindService {
  authUri: string = 'https://creator.douyin.com/creator-micro/content/upload';
  prepare(accountId: number): Observable<IWonewSer.BindAccountTask> {
    const task: IWonewSer.BindAccountTask = {
      accountId,
      hash: IHelper.hash(24),
      startAt: Date.now(),
      state: 0,
    }
    IGlobal.bindAccountTaskSet[task.hash] = task;
    const webAjaxResponseSubject = new ReplaySubject<{type: "poll" | "generate", data: any}>()
    const loginButtonSelector = `#root > div > section > header > div.creator-header-wrapper > div > div > div > div.semi-navigation-footer > span`
    // @ts-ignore
    return defer(() => 
      puppeteer.launch({
        defaultViewport: {
          width: 1920,
          height: 720,
        },
        // headless: true,
        headless: false,
        args: ['--no-sandbox','--disable-setuid-sandbox'],
      }),
    ).pipe(
      tap((browser) => (task.browser = browser)),
      concatMap(() => task.browser.newPage()),
      tap((page) => (task.page = page)),
      concatMap(() => task.page.setRequestInterception(true)),
      map(() => {
        task.page.on('request', (interceptedRequest: puppeteer.HTTPRequest) => {
          if (interceptedRequest.isInterceptResolutionHandled()) return;
          if (
            interceptedRequest.url().endsWith('.png') ||
            interceptedRequest.url().endsWith('.jpg')
          ) {
            interceptedRequest.abort();
          } else {
            interceptedRequest.continue();
          }
        });
        task.page.on('response', async (res: puppeteer.HTTPResponse) => {
          const req: puppeteer.HTTPRequest = res.request()
          if (req.url().includes(`check_qrconnect`) && req.method().toUpperCase() === "GET") {
            let resJson = await res.json().catch(e => undefined)
            webAjaxResponseSubject.next({type: "poll", data: resJson})
            let stateCodeSet = {
              "1": 0, "2": 1, "5": 2, 
            }
            if (typeof stateCodeSet[resJson?.data?.code] === "number") {
              task.state = stateCodeSet[resJson?.data?.status]
            }
            
          }
          if (req.url().includes(`get_qrcode`) && req.method().toUpperCase() === "GET") {
            let resJson = await res.json().catch(e => undefined)
            webAjaxResponseSubject.next({type: "generate", data: resJson})
          }
        });
      }),
      concatMap(() => task.page.goto(this.authUri)),
      concatMap(() => task.page.waitForSelector(loginButtonSelector)),
      concatMap(() => task.page.click(loginButtonSelector)),
      concatMap(() => {
        return new Observable<string>((observer => {
          webAjaxResponseSubject.subscribe(
            res => {
              if (res.type === "generate" && res.data?.data?.qrcode_index_url) {
                observer.next(res.data?.data?.qrcode_index_url)
                observer.complete()
              }
            }
          )
        }))
      }),
      tap(targeUri => task.targetUri = targeUri),
      map(() => task),
      timeout(15000),
      catchError(error => {
        task.browser?.close().catch(e => e)
        throw error
      })
    )
  }
  check(hash: string) {
    // 
    return of(IGlobal.bindAccountTaskSet[hash]).pipe(
      map(task => {
        if (!task || task.state === 2) {
          throw new HttpException(`Hash expired.`, -2000)
        }
        return task
      }),
      concatMap(task => {
        if (task.page.url() !== "https://creator.douyin.com/creator-micro/home") {
          return of(task)
        }
        return from(task.page.cookies()).pipe(
          map(cookies => {
            task.browser.close().catch(e => e)
            task.cookie = JSON.stringify(cookies)
            IGlobal.bindAccountTaskSet[hash] = undefined
            return task
          }),
          concatMap(task => {
            return this.checkCookie(task.cookie).pipe(
              map(({platformUser}) => {
                if (!platformUser) {
                  throw new HttpException(`Get platform user by cookie failed.`, -2001)
                }
                task.state = 3
                task.platformUser = platformUser
                return task
              })
            )
          })
        ) 
      })
    )
  }

  checkCookie(cookie: string): Observable<{valid: boolean, cookie?: string, platformUser?: any}> {
    return defer(async () => {
      let browser: puppeteer.Browser | undefined = undefined
      try {
        browser = await puppeteer.launch({
          headless: true,
          args: ['--no-sandbox','--disable-setuid-sandbox'],
        })
        const page = await browser.newPage()
        const cookies = JSON.parse(cookie) as puppeteer.Cookie[]
        for(let i = 0; i < cookies.length; i++){
          await page.setCookie(cookies[i])
        }
        await page.goto("https://creator.douyin.com/creator-micro/content/upload?enter_from=dou_web")
        const userInfoRes = await page.waitForResponse(res => res.url().includes("web/api/media/user/info"))
        const userInfoResJson = await userInfoRes.json()
        const updatedCookies = JSON.stringify(await page.cookies())
        await browser.close().catch(e => e)
        if (userInfoResJson.status_code === 0) {
          return {valid: true, platformUser: userInfoResJson.user, cookie: updatedCookies}
        }
        return {valid: false, platformUser: null, cookie: ""}
      } catch(e) {
        browser && browser.close().catch(e => e)
        throw new HttpException("Check dy cookie failed.", HttpStatus.BAD_REQUEST)
      }
    })
  }
}